﻿using Newtonsoft.Json;
using River.Core;
using River.IntAI.ISdk;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace River.IntAI
{
    public partial class FrmSpeech : BaseForm
    {
        string destFile = null;// 目标文件
        decimal duration = 0;// 视频长度
        decimal current_pt = 0;// 分割时当前时间
        int PER_LENGTH = 60;// 分割时长
        StreamsInfo curStream = null;// 音视频信息
        bool showFileInfo = true;/// 显示文件信息标志
        string[] srcFiles = null;// 待处理文件列表
        int cur_file_index = 0;// 当前处理文件序号
        int defaultRate = 8000;// 默认采样率
        bool ffmpegEnable = false; // FFMpeg组件是否可用
        StringBuilder fileInfoBuilder = new StringBuilder();

        bool _seperateFlag;
        bool SeperateFlag// 分割音频处理标志
        {
            get { return _seperateFlag; }
            set
            {
                _seperateFlag = value;
                updateSeparateStatus();
            }
        }

        private bool _pcmFlag;// 转换音频处理标志
        private bool ConvertPcmFlag
        {
            get { return _pcmFlag; }
            set
            {
                _pcmFlag = value;
                updateConvertStatus();
            }
        }
        private bool _wordFlag;// 文字识别处理标志
        private bool ConvertWordFlag
        {
            get { return _wordFlag; }
            set
            {
                _wordFlag = value;
                updateWordStatus();
            }
        }

        public FrmSpeech()
            :base()
        {
            InitializeComponent();
            cbAudioFormat.SelectedIndex = 0;

            if (FFmpegUtils.FFmpegOutput == null)
            {
                FFmpegUtils.FFmpegOutput += FFmpegOutput;// 注册事件
            }
            // 读取配置
            chbShowDetail.Checked= IniUtil.INIGetStringValue(IniSection.Other, IniKey.ShowFileInfo,"0") == "1";
            showFileInfo = chbShowDetail.Checked;
            rtbCmd.Text = IniUtil.INIGetStringValue(IniSection.Other, IniKey.FFmpegCmd);
            tbdestRate.Text = IniUtil.INIGetStringValue(IniSection.Other, IniKey.DefaultRate, defaultRate.ToString());
            cbAudioFormat.Text = IniUtil.INIGetStringValue(IniSection.Other, IniKey.AudioFormat, "m4a");
            tbTimeLength.Text = IniUtil.INIGetStringValue(IniSection.Other, IniKey.SeperateLength, "600");
        }
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            string ffmpegPath = FFmpegBinariesHelper.RegisterFFmpegBinaries(false);
            ffmpegEnable = !string.IsNullOrEmpty(ffmpegPath);
            if (!ffmpegEnable) ShowMainStatus("没有发现FFMpeg组件，相关功能不可用");
            // FFmpeg 组件功能
            btnConvertRate.Enabled = ffmpegEnable;
            btnExtract.Enabled = ffmpegEnable;
            btnAudio2Pcm.Enabled = ffmpegEnable;
            btnExeCmd.Enabled = ffmpegEnable;
            
            tbdestRate.Enabled = ffmpegEnable;
            cbAudioFormat.Enabled = ffmpegEnable;
            chbShowDetail.Enabled = ffmpegEnable;
            tbTimeLength.Enabled = ffmpegEnable;
            rtbCmd.Enabled = ffmpegEnable;
        }
        
        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
            // 保存用户输入
            IniUtil.INIWriteValue(IniSection.Other, IniKey.FFmpegCmd, rtbCmd.Text);
            IniUtil.INIWriteValue(IniSection.Other, IniKey.DefaultRate, tbdestRate.Text);
            IniUtil.INIWriteValue(IniSection.Other, IniKey.ShowFileInfo, chbShowDetail.Checked ? "1" : "0");
            IniUtil.INIWriteValue(IniSection.Other, IniKey.AudioFormat, cbAudioFormat.Text);
            IniUtil.INIWriteValue(IniSection.Other, IniKey.SeperateLength, tbTimeLength.Text);
        }
        /// <summary>
        /// 选择文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBrowse_Click(object sender, EventArgs e)
        {
            //tbSampleRate.Text = string.Empty;
            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                ofd.Multiselect = false;
                ofd.RestoreDirectory = true;
                if (ofd.ShowDialog() == DialogResult.OK)
                    tbFileName.Text = ofd.FileName;
                else
                    return;
            }
            if (Path.GetExtension(tbFileName.Text).ToLower() != ".pcm")
            {
                btnViewInfo();// 识别信息
            }
        }
        /// <summary>
        /// 开始识别文字
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStart_Click(object sender, EventArgs e)
        {
            ConvertWordFlag = !ConvertWordFlag;
            if (!ConvertWordFlag)
            {
                return;
            }
            srcFiles = new string[1] { tbFileName.Text };
           
            startConvertSpeech2Word();
        }
        /// <summary>
        /// 批量转换文字
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBatchAudio2Word_Click(object sender, EventArgs e)
        {
            ConvertWordFlag = !ConvertWordFlag;
            if (!ConvertWordFlag)
            {
                return;
            }
            cur_file_index = 0;

            if (!Utils.selectMulFiles(out srcFiles))
            {
                ConvertWordFlag = false;
                return;
            }
            startConvertSpeech2Word();
        }
        /// <summary>
        /// 音频转换成文字
        /// </summary>
        private void startConvertSpeech2Word()
        {
            if (srcFiles == null || srcFiles.Length <= 0) return;
            cur_file_index = 0;
            ConvertWordFlag = true;
            ShowStatus("正在识别文字...");
            Thread thread = new Thread(speech2WordThread);
            thread.IsBackground = true;
            thread.Start();
        }
        /// <summary>
        /// 音频转换成文字线程
        /// </summary>
        private void speech2WordThread()
        {
            int sampleRate = GetSampleRate(tbSampleRate, defaultRate);
            while (true)
            {
                if (cur_file_index >= srcFiles.Length || !ConvertWordFlag)
                {
                    ShowStatus("转换完成");
                    ConvertWordFlag = false;
                    return;
                }

                string srcPath = srcFiles[cur_file_index];
                SpeechUtils.ConvertSpeedToWord(speedEndEvent, srcPath, sampleRate);
                cur_file_index++;
            }
        }
        /// <summary>
        /// 识别文字结束事件
        /// </summary>
        /// <param name="speechObj"></param>
        public void speedEndEvent(RetSpeech speechObj)
        {
            if (speechObj.err_no == 0)
                ShowStatus(speechObj.result[0], false, true);
            else
                ShowStatus(string.Format("转换失败，错误码：[{0}]，错误描述：[{1}]", speechObj.err_no, speechObj.err_msg));
        }
        /// <summary>
        /// 更新识别文字按钮提示文字
        /// </summary>
        private void updateWordStatus()
        {
            this.InvokeDele(new Action(() =>
            {
                if (ConvertWordFlag)
                {
                    btnBatchAudio2Word.Text = "停止";
                    btnStart.Text = "停止";
                }
                else
                {
                    btnBatchAudio2Word.Text = "批量识别文字";
                    btnStart.Text = "音频识别文字";
                    SpeechUtils.ConvertFlag = false;
                }
            }));
        }

        /// <summary>
        /// 读取用户输入采样率
        /// </summary>
        /// <param name="tb"></param>
        /// <returns></returns>
        private int GetSampleRate(TextBox tb, int defaultRate = 0)
        {
            int sampleRate = 0;
            if (!int.TryParse(tb.Text, out sampleRate) || sampleRate == 0)
            {
                ShowStatus("采样率无效，使用默认采样率："+ defaultRate, false);
                return defaultRate;
            }
            return sampleRate;
        }
        /// <summary>
        /// 显示状态信息
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="skiptime"></param>
        private void ShowStatus(string msg, bool checkShow = false, bool skiptime = false)
        {
            if (checkShow && !showFileInfo) return;
            this.InvokeDele(new Action(() =>
            {
                rtbStatus.AppendText((skiptime ? "" : Utils.NowTime() + ": ") + msg + "\r\n");
                rtbStatus.Select(rtbStatus.Text.Length, 0);
                rtbStatus.ScrollToCaret();
            }));
        }
        /// <summary>
        /// 查看信息
        /// </summary>
        private void btnViewInfo()
        {
            if (!checkFile() || !ffmpegEnable)
                return;
            fileInfoBuilder.Clear();
            duration = 0;
            ShowStatus("以下是文件信息：");
            FFmpegUtils.Start(FFmpegUtils.Args_ViewInfo(tbFileName.Text), ViewFinish);
        }
        /// <summary>
        /// 读取信息完成事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ViewFinish(object sender, EventArgs e)
        {
            FFmpegRetDetail retDetail = JsonConvert.DeserializeObject<FFmpegRetDetail>(fileInfoBuilder.ToString());
            if (retDetail != null && retDetail.streams != null)
            {
                curStream = retDetail.streams.Where(x => x.codec_type == "audio").FirstOrDefault();
                if(curStream != null)
                {
                    Invoke(new Action(() =>
                    {
                        tbSampleRate.Text = curStream.sample_rate;
                        decimal.TryParse(curStream.duration, out duration);
                    }));
                }
            }
            else
            {
                ShowStatus("无法识别音频信息", true);
            }
            ShowStatus("读取文件信息完毕.", true);
        }
        /// <summary>
        /// 检测用户选择的文件
        /// </summary>
        /// <returns></returns>
        private bool checkFile()
        {
            string fileName = tbFileName.Text;

            if (string.IsNullOrEmpty(fileName) || !File.Exists(fileName))
            {
                MsgUtil.Error("请选择有效的音视频文件");
                return false;
            }
            return true;
        }
        // 信息提示
        private void FFmpegOutput(object sender, DataReceivedEventArgs e)
        {
            if (!string.IsNullOrEmpty(e.Data))
            {
                fileInfoBuilder.AppendLine(e.Data);
                ShowStatus(e.Data,true, true);
            }
        }

        /// <summary>
        /// 视频中提取音频
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExtract_Click(object sender, EventArgs e)
        {
            if (!checkFile()) return;
            ShowStatus("开始提取音频...");
            string args = null;
            int sampleRate = GetSampleRate(tbdestRate, defaultRate);
            buildDestFileName(tbFileName.Text, cbAudioFormat.Text, sampleRate);
            if (cbAudioFormat.Text == "mp3")
            {
                args = FFmpegUtils.Args_ConvertVideo2mp3(tbFileName.Text, destFile, sampleRate);
            }
            else if (cbAudioFormat.Text == "pcm")
            {
                args = FFmpegUtils.Args_ConvertVideo2pcm(tbFileName.Text, destFile, sampleRate);
            }
            else
                args = FFmpegUtils.Args_ConvertVideo2Audio(tbFileName.Text, destFile, sampleRate); // 默认m4a
            FFmpegUtils.Start( args, extractFinish);
        }

        /// <summary>
        /// 生成自动目标文件名
        /// </summary>
        /// <param name="srcPath">源文件全名</param>
        /// <param name="destExt">目标扩展名</param>
        /// <param name="rate">目标采样率</param>
        /// <returns></returns>
        private bool buildDestFileName(string srcPath, string destExt, int rate)
        {
            string srcExt = Path.GetExtension(srcPath);

            // 如果目标文件类型为空，保留原来的扩展名
            if (string.IsNullOrEmpty(destExt))
            {
                destExt = srcExt;
            }
            destFile = string.Format("{0}_{1}_{2}k.{3}", srcPath.Replace(srcExt, ""), DateTime.Now.ToString("yyMMddHHmmss"), rate / 1000, destExt.TrimStart('.'));
            return true;
        }

        private void extractFinish(object sender, EventArgs e)
        {
            ShowStatus("音频转换完成！\r\n音频文件详见：" + destFile, false);
        }
        /// <summary>
        /// 转换采样率
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnConvertRate_Click(object sender, EventArgs e)
        {
            int sampleRate = GetSampleRate(tbdestRate,defaultRate);
            if (!checkFile()) return;
            ShowStatus("开始转换...");
            string srcPath = tbFileName.Text;
            string ext = Path.GetExtension(srcPath);
            destFile = srcPath.Replace(ext, ("_" + (sampleRate / 1000) + "k" + ext));
            string args = FFmpegUtils.Args_ConvertRate(srcPath, destFile, sampleRate);
            FFmpegUtils.Start(args, convertFinish);
        }
        /// <summary>
        /// 转换完成事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void convertFinish(object sender, EventArgs e)
        {
            ShowStatus("音频转换完成！\r\n音频文件详见：" + destFile,false);
        }
        /// <summary>
        /// 分割音频
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSeperate_Click(object sender, EventArgs e)
        {
            if (!int.TryParse(tbTimeLength.Text, out PER_LENGTH) || PER_LENGTH <= 0)
                PER_LENGTH = 60;

            SeperateFlag = !SeperateFlag;
            if (duration == 0)
            {
                ShowStatus("音频信息读取失败");
                return;
            }
            current_pt = 0;
            ShowStatus("开始分割音频...");
            
            startSeperate();
        }
        /// <summary>
        /// 更新分割音频的按钮状态
        /// </summary>
        private void updateSeparateStatus()
        {
            this.InvokeDele(new Action(() =>
            {
            if (SeperateFlag)
                btnSeperate.Text = "停止";
            else
                btnSeperate.Text = "分割音频";
        }));
        }

        private void startSeperate()
        {
            if (current_pt >= duration || !SeperateFlag)
            {
                ShowStatus("音频分割完成" );
                SeperateFlag = false;
                return;
            }

            string srcPath = tbFileName.Text;
            string destDir = srcPath.Replace(Path.GetExtension(srcPath), string.Empty);
            if (!Directory.Exists(destDir))
                Directory.CreateDirectory(destDir);
            destFile = Path.Combine(destDir, (current_pt / PER_LENGTH +1) + Path.GetExtension(srcPath));
            decimal timeLength = duration > current_pt + PER_LENGTH ? PER_LENGTH: duration - current_pt;
            string args = FFmpegUtils.Args_Seperate(srcPath, destFile, buildTime(current_pt), buildTime(timeLength));
            FFmpegUtils.Start(args, seperateFinish);
            current_pt += timeLength;

        }
        /// <summary>
        /// 分割完成
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void seperateFinish(object sender, EventArgs e)
        {
            ShowStatus("音频分割文件：" + destFile, false);
            startSeperate();
        }
        /// <summary>
        /// spanTime 形式
        /// </summary>
        /// <param name="seconds"></param>
        /// <returns></returns>
        private string buildTime(decimal seconds)
        {
            TimeSpan time = new TimeSpan((long)(seconds * 10000000));
            //return time.ToString("HH:mm:ss.fff");
            return string.Format("{0:d2}:{1:d2}:{2:d2}.{3:d3}", time.Hours, time.Minutes, time.Seconds, time.Milliseconds);
        }
        /// <summary>
        /// audio 转pcm
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAudio2Pcm_Click(object sender, EventArgs e)
        {
            ConvertPcmFlag = !ConvertPcmFlag;
            if (!ConvertPcmFlag)
            {
                return;
            }
            cur_file_index = 0;

            if (!Utils.selectMulFiles(out srcFiles))
            {
                return;
            }
            ShowStatus("开始转换...");
            ConvertPcmFlag = true;
            startConvertAudio2Pcm();
        }
        // 音频转换成pcm
        private void startConvertAudio2Pcm()
        {
            if (srcFiles == null || srcFiles.Length <= 0) return;
            if (cur_file_index >= srcFiles.Length || !ConvertPcmFlag)
            {
                ShowStatus("转换完成");
                ConvertPcmFlag = false;
                return;
            }

            int sampleRate = GetSampleRate(tbdestRate, defaultRate);
            string srcPath = srcFiles[cur_file_index];
            string ext = Path.GetExtension(srcPath);
            buildDestFileName(srcPath, ".pcm", sampleRate);
            string args = FFmpegUtils.Args_Audio2pcm(srcPath, destFile, sampleRate);
            FFmpegUtils.Start(args, convertPcmFinish);
            cur_file_index++;
        }
        private void convertPcmFinish(object sender, EventArgs e)
        {
            ShowStatus("音频转换完成，详见：" + destFile, false);
            startConvertAudio2Pcm();
        }
        /// <summary>
        /// 更新音频转换按钮提示
        /// </summary>
        private void updateConvertStatus()
        {
            this.InvokeDele(new Action(() =>
            {
                if (ConvertPcmFlag)
                    btnAudio2Pcm.Text = "停止";
                else
                    btnAudio2Pcm.Text = "音频转Pcm";
            }));
        }

        /// <summary>
        /// 清空状态
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnEmptyStatus_Click(object sender, EventArgs e)
        {
            this.InvokeDele(new Action(() =>
            {
                rtbStatus.Text = string.Empty;
            }));
        }
        /// <summary>
        /// 是否显示文件信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            showFileInfo = chbShowDetail.Checked;
        }
        /// <summary>
        /// 填入默认采样率
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDefaultRate_Click(object sender, EventArgs e)
        {
            tbSampleRate.Text = defaultRate.ToString();
        }
        /// <summary>
        /// 打开目标路径
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOpenDestDir_Click(object sender, EventArgs e)
        {
            Utils.OpenDir(destFile, tbFileName);
        }
        /// <summary>
        /// 执行自定义FFmpeg命令
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExeCmd_Click(object sender, EventArgs e)
        {
            string cmd = rtbCmd.Text.ToLower().Replace("%file", tbFileName.Text).Replace("%rate1", tbSampleRate.Text).Replace("%rate2" , tbdestRate.Text).Replace("%type", cbAudioFormat.Text).Trim();
            if (string.IsNullOrEmpty(cmd))
            {
                ShowStatus("请在右侧黄色文本框中输入命令(文件路径、采样率、目标采样率、目标类型可分别用 %file, %rate1, %rate2, %type 代替)。");
                return;
            }
            ShowStatus("开始执行FFmpeg命令："+ cmd);
            FFmpegUtils.Start(cmd, exeCmdFinish);
        }
        private void exeCmdFinish(object sender, EventArgs e)
        {
            ShowStatus("命令执行完毕");
        }
    }

}
